import datetime
import logging
import subprocess
import sys
import time
from multiprocessing import Process

from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QGridLayout, QPushButton, QWidget, QLabel, QMessageBox, QDialog

from school_ringer_modules.calendar_editor import CalendarEditor, is_holiday_today
from school_ringer_modules.config import sound_path, config_path
from school_ringer_modules.config import version
from school_ringer_modules.design import PlayMusicOnBreaksDialog
from school_ringer_modules.groups_editor import GroupsEditor
# from school_ringer_modules.notification import NotificationWindow
from school_ringer_modules.schedule_editor import ScheduleEditor
from school_ringer_modules.schedules_dataclasses import schedules
from school_ringer_modules.sound import default_sound
from school_ringer_modules.system import run_command, get_current_user_on_x


class UpdateLabels(QThread):
    update_time = pyqtSignal(str)
    service_status = pyqtSignal(str)
    active_schedules = pyqtSignal(str)
    next_call = pyqtSignal(str)

    def __init__(self, parent=None):
        QThread.__init__(self, parent)
        self.continue_run = True

    def get_service_status(self) -> str:
        """
        Возвращает текущий статус фоновой службы, отвечающей за запуск звонков
        :return: active если сервис активен, иначе inactive
        """
        return subprocess.run("systemctl is-active school-ringer.service",
                              shell=True, capture_output=True).stdout.decode().strip()

    def get_next_call(self) -> str:
        """
        Возвращает строку с информацией о следующем звонке
        @return: время следующего звонка в формате DD.MM.YY HH:MM или информацию о том, что звонок не запланирован
        """
        if self.get_service_status() != 'active':
            return '-'
        info = subprocess.run('journalctl -u school-ringer.service | grep -v "звонок: -" | grep звонок | tail -1',
                              capture_output=True, shell=True).stdout.decode()

        if self.get_active_schedules() == 'Нет активных расписаний':
            return '-'
        if 'звонок' not in info:
            # logging.info(f'Не удалось найти звонок в строке: {info}')
            return 'Звонок не запланирован'
        if is_holiday_today():
            return 'Идут каникулы. Звонки отключены'
        date, time = info.split('звонок: ')[-1].strip().split()
        year, month, day = date.split('-')
        hour, minute, second = time.split(':')
        return f'{day}.{month}.{year} {hour}:{minute}'

    def get_active_schedules(self) -> str:
        """
        Получение списка активных на текущий момент расписаний
        Возвращает текстовую строку, где они перечислены через запятую
        @return: строка из названий расписаний, записанных через запятую
        """
        ans = []
        for every_schedule in schedules.get_all_schedules_names():
            if schedules[every_schedule].active is True:
                ans.append(every_schedule)
        if not ans:
            return 'Нет активных расписаний'
        return ', '.join(ans)

    def update_string_time(self) -> str:
        """
        Возвращает текущее время в формате HH:MM
        @return: строка с текущим временем в указанном формате
        """
        current_time = datetime.datetime.now()
        # return f'{current_time.hour:02}:{current_time.minute:02}'
        return current_time.strftime('%H:%M')

    def run(self):
        while self.continue_run:
            self.update_time.emit(self.update_string_time())
            self.service_status.emit(self.get_service_status())
            self.active_schedules.emit(self.get_active_schedules())
            self.next_call.emit(self.get_next_call())
            # self.holidays_define.emit(self.get_holidays_status())
            time.sleep(0.1)


class CallsSettings(QWidget):
    def __init__(self, app) -> None:
        """
        Базовый конструктор класса
        """
        super().__init__()
        self.initUI()
        self.app = app
        self.thread = UpdateLabels()
        self.thread.update_time.connect(self.update_time)
        self.thread.service_status.connect(self.update_service_status)
        self.thread.active_schedules.connect(self.update_active_schedules)
        self.thread.next_call.connect(self.set_next_lesson_label)
        self.thread.start()

    def update_time(self, time_string: str) -> None:
        """
        Обновляет текущее время
        @param time_string: строка с текущим временем
        """
        self.label_real_clock.setText(time_string)

    def ring_the_call(self, filename: str) -> None:
        """
        Функция вызывает воспроизведение звонка
        @rtype: None
        @param filename: имя файла для воспроизведения
        """
        try:
            current_user_on_x = get_current_user_on_x()
            if current_user_on_x:
                run_command('pkill play')
                # if self.music_playing_thread.isRunning():
                #     self.music_playing_thread.terminate()
                player = Process(target=lambda: subprocess.run(
                    f'play "{sound_path}/{filename}"', shell=True), args=()
                                 )
                player.start()

                logging.info('Выполнен принудительный звонок')
            else:
                logging.info('Не найден активный пользователь. Не удалось выполнить принудительный звонок.')
        except Exception as e:
            logging.info(f'Возникло исключение: {e}. Не удалось выполнить принудительный звонок.')

    def call_now(self) -> None:
        """
        Функция воспроизводит звонок звуком по умолчанию при нажатии кнопки (с подтверждением)
        @rtype: None
        """
        dlg = QMessageBox(self)
        dlg.setWindowTitle("Внимание!")
        dlg.setText("Вы действительно хотите позвонить сейчас?")
        dlg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        dlg.setIcon(QMessageBox.Question)
        button = dlg.exec()

        if button == QMessageBox.Yes:
            self.ring_the_call(default_sound())

    def edit_schedule(self) -> None:
        """
        Открытие окна редактирования расписания
        @rtype: None
        """
        try:
            schedule_editor = self.schedule_editor
            self.schedule_editor.close()
            logging.info(f'Закрыто окно редактирования расписаний')
        except AttributeError:
            logging.info(f'Окно для редактирования расписаний не было активно')
            pass
        finally:
            self.schedule_editor = ScheduleEditor()
            self.schedule_editor.settings_window = self
            # for i in range(len(self.schedule_editor.tabs)):
            #     self.schedule_editor.tabs[i].save_schedule_button.clicked.connect(self.update_active_schedules)
            logging.info(f'Открыто новое окно редактирования расписаний')

    def edit_groups(self) -> None:
        """
        Открытие окна редактирования групп звонков
        @rtype: None
        """
        try:
            self.groups_editor.close()
            logging.info(f'Закрыто окно редактирования групп звонков')
        except Exception:
            logging.info(f'Окно редактирования групп звонков было неактивным')
        finally:
            self.groups_editor = GroupsEditor()
            self.groups_editor.parent = self
            logging.info(f'Открыто новое окно редактирования групп звонков')

    def switch_service(self) -> None:
        """
        Включение и выключение фоновой службы, запускающей звонки
        @rtype: None
        """
        if run_command('systemctl is-active school-ringer.service').strip() == 'active':
            run_command('pkexec systemctl disable --now school-ringer.service')
            self.btn_switch_service.setText('Включить сервис')
            logging.info(f'Сервис выключен')
        else:
            self.btn_switch_service.setText('Выключить сервис')
            run_command('pkexec sh -c "systemctl enable school-ringer.service && '
                        'systemctl restart school-ringer.service"')
            logging.info(f'Сервис включён')

    def update_active_schedules(self, string_of_schedules) -> None:
        """
        Обновляет список активных расписаний в окне настроек
        @rtype: None
        @param string_of_schedules: список расписаний для отображения
        """
        if self.real_active_schedules_label.text() != string_of_schedules:
            self.real_active_schedules_label.setText(string_of_schedules)
            logging.info(f'Список активных расписаний в основном окне обновлён')

    def update_service_status(self, message: str) -> None:
        """
        Обновляет статус сервиса в окне приложения
        @param message: статус сервиса (active/inactive)
        """
        if message == 'active':
            text_status = 'Работает'
            self.real_service_status_label.setStyleSheet('font: bold; color: green')
        else:
            text_status = 'Остановлен'
            self.real_service_status_label.setStyleSheet('font: bold; color: red')
        self.real_service_status_label.setText(text_status)
        if message == 'active':
            self.btn_switch_service.setText('Выключить сервис')
        else:
            self.btn_switch_service.setText('Включить сервис')

    def set_next_lesson_label(self, next_call_text: str) -> None:
        """
        Обновляет время следующего звонка в окне приложения
        @param next_call_text: строка со временем следующего звонка
        """
        if self.real_next_call_label.text() != next_call_text:
            self.real_next_call_label.setText(next_call_text)
            logging.info(f'Установлено время следующего звонка: {next_call_text}')

    def closeEvent(self, a0: QCloseEvent) -> None:
        """
        Обработка закрытия окна настроек
        @param a0: событие, вызвавшее закрытие окна
        """
        try:
            logging.info(f'Приложение завершило работу')
            sys.exit(self.app)
        except Exception:
            logging.info(f'sys.exit не сработал O_O')

    # def make_notification(self):
    #     self.notification_box = NotificationWindow()

    def edit_calendar(self) -> None:
        """
        Открытие окна редактирования каникул
        """
        try:
            self.holidays_editing_window.close()
        except Exception:
            logging.info('Окно редактирования каникул было неактивным')
        self.holidays_editing_window = CalendarEditor()
        self.holidays_editing_window.parent = self

    def play_music_on_breaks(self) -> None:
        """
        Если музыка на переменах не играла, открывает окно выбора музыки, иначе отключает воспроизведение музыки на
        переменах
        """
        # если музыка отключена
        if run_command(f'py-ini-config get {config_path}/school-ringer.conf Main is_breaks_music_on').strip() != 'true':
            dlg = PlayMusicOnBreaksDialog(self)
            if dlg.exec() == QDialog.Accepted:
                music_folder = f'{dlg.music_folder.strip()}' if dlg.music_folder else \
                    run_command(f'py-ini-config get {config_path}/school-ringer.conf Main music_folder').strip()
                # self.music_playing_thread.songs_list = [
                #     f'"{music_folder}/{i}"' for i in os.listdir(music_folder) if (
                #             i.endswith('.mp3') or i.endswith('.ogg') or i.endswith('.wav')
                #     )]
                # if dlg.shuffle_checkbox.isChecked():
                #     shuffle(self.music_playing_thread.songs_list)

                cmd = subprocess.run(f'pkexec sh -c "py-ini-config set '
                                     f'{config_path}/school-ringer.conf Main is_breaks_music_on true && '
                                     f'py-ini-config set '
                                     f'{config_path}/school-ringer.conf Main music_folder \\\"{music_folder}\\\" && '
                                     f'py-ini-config set '
                                     f'{config_path}/school-ringer.conf Main music_shuffle '
                                     f'{dlg.shuffle_checkbox.isChecked()} &&'
                                     f'py-ini-config set '
                                     f'{config_path}/school-ringer.conf Main music_volume '
                                     f'{dlg.slider.value() if dlg.low_music_level_checkbox.isChecked() else 100} && '
                                     f'py-ini-config set '
                                     f'{config_path}/school-ringer.conf Main music_pause '
                                     f'{dlg.pause_minutes.value() * 60 + dlg.pause_seconds.value() if dlg.pause_required_checkbox.isChecked() else 0}"',
                                     shell=True)
                if cmd.returncode == 0:
                    # self.music_playing_thread.start()
                    self.play_music_on_breaks_btn.setText('Не играть музыку')
                    self.real_music_on_breaks_status_label.setText(f'Играет из папки {music_folder}')
                    self.is_breaks_music_on = True

        else:
            if subprocess.run(f'pkexec sh -c "py-ini-config set {config_path}/school-ringer.conf Main '
                              f'is_breaks_music_on false"', shell=True).returncode == 0:
                # self.music_playing_thread.terminate()
                subprocess.run('pkill play', shell=True)
                self.play_music_on_breaks_btn.setText('Играть музыку на переменах')
                self.real_music_on_breaks_status_label.setText(f'Отключена')
                self.is_breaks_music_on = False

    def download_instructions(self) -> None:
        """
        Открывает в браузере инструкцию по использованию приложения
        """
        try:
            current_user_on_x = get_current_user_on_x()
            if current_user_on_x:
                run_command(f'chromium-browser '
                            f'https://hub.mos.ru/mos/src/school-ringer/-/blob/master/instruction.pdf')
            else:
                logging.info('Не найден пользователь с графической оболочкой. Невозможно открыть инструкцию')
        except Exception as e:
            logging.info(f'Возникло исключение: {e}. Невозможно открыть инструкцию.')

    def initUI(self) -> None:
        """
        Инициализация графического интерфейса
        """
        grid = QGridLayout()
        self.setLayout(grid)
        self.instructions_link = QLabel('Скачать инструкцию')
        self.instructions_link.setOpenExternalLinks(True)
        self.instructions_link.setStyleSheet('color: blue; text-decoration: underline; cursor: hand')
        self.instructions_link.mousePressEvent = lambda event: self.download_instructions()
        self.instructions_link.setCursor(Qt.PointingHandCursor)
        grid.addWidget(self.instructions_link, 0, 0, 1, 2)
        # notification_button = QPushButton('🎤 Выполнить оповещение')
        # notification_button.clicked.connect(self.make_notification)
        # grid.addWidget(notification_button, 0, 2, 1, 2)
        btn_call_now = QPushButton('🔔 Принудительный звонок')
        btn_call_now.clicked.connect(self.call_now)
        grid.addWidget(btn_call_now, 0, 2, 1, 2)
        label_current_time = QLabel('Текущее время: ')
        grid.addWidget(label_current_time, 1, 1)
        current_time = datetime.datetime.now()
        self.label_real_clock = QLabel(f'{current_time.hour:02}:{current_time.minute:02}')
        grid.addWidget(self.label_real_clock, 1, 2)
        label_active_schedule = QLabel('Активное расписание: ')
        grid.addWidget(label_active_schedule, 2, 1)
        self.real_active_schedules_label = QLabel()
        grid.addWidget(self.real_active_schedules_label, 2, 2)
        label_next_call = QLabel('Следующий звонок: ')
        grid.addWidget(label_next_call, 3, 1)
        self.real_next_call_label = QLabel('Сервис выключен')
        self.real_next_call_label.setWordWrap(True)
        grid.addWidget(self.real_next_call_label, 3, 2)

        label_music_on_breaks_status = QLabel('Музыка на переменах: ')
        label_music_on_breaks_status.setAlignment(Qt.AlignRight)
        label_music_on_breaks_status.setStyleSheet("QLabel {font-weight: bold;}")
        grid.addWidget(label_music_on_breaks_status, 4, 1)
        self.real_music_on_breaks_status_label = QLabel()
        self.real_music_on_breaks_status_label.setWordWrap(True)
        if run_command(f'py-ini-config get {config_path}/school-ringer.conf Main is_breaks_music_on').strip() != 'true':
            self.real_music_on_breaks_status_label.setText('Отключена')
        else:
            music_folder = run_command(f'py-ini-config get {config_path}/school-ringer.conf Main music_folder').strip()
            self.real_music_on_breaks_status_label.setText(f'Играет из папки {music_folder}')
        grid.addWidget(self.real_music_on_breaks_status_label, 4, 2)

        label_service_status = QLabel('Статус сервиса: ')
        grid.addWidget(label_service_status, 5, 1)
        for text_label in (label_current_time, label_active_schedule,
                           label_next_call, label_service_status):
            text_label.setAlignment(Qt.AlignRight)
            text_label.setStyleSheet("QLabel {font-weight: bold;}")
        self.real_service_status_label = QLabel()
        grid.addWidget(self.real_service_status_label, 5, 2)

        self.btn_switch_service = QPushButton('Выключить сервис')
        self.btn_switch_service.setMinimumHeight(40)
        self.btn_switch_service.clicked.connect(self.switch_service)
        grid.addWidget(self.btn_switch_service, 6, 1, 1, 2)
        btn_edit_schedule = QPushButton('Редактировать расписание')
        btn_edit_schedule.setMinimumHeight(40)
        btn_edit_schedule.clicked.connect(self.edit_schedule)
        grid.addWidget(btn_edit_schedule, 7, 1, 1, 2)
        btn_edit_groups = QPushButton('Редактировать группы звонков')
        btn_edit_groups.setMinimumHeight(40)
        btn_edit_groups.clicked.connect(self.edit_groups)
        grid.addWidget(btn_edit_groups, 8, 1, 1, 2)
        btn_edit_calendar = QPushButton('Установить каникулы')
        btn_edit_calendar.setMinimumHeight(40)
        btn_edit_calendar.clicked.connect(self.edit_calendar)
        grid.addWidget(btn_edit_calendar, 9, 1, 1, 2)
        self.play_music_on_breaks_btn = QPushButton('')
        if run_command(f'py-ini-config get {config_path}/school-ringer.conf Main is_breaks_music_on').strip() == 'true':
            self.play_music_on_breaks_btn.setText('Не играть музыку')
        else:
            self.play_music_on_breaks_btn.setText('Играть музыку на переменах')
        self.play_music_on_breaks_btn.setMinimumHeight(40)
        self.play_music_on_breaks_btn.clicked.connect(self.play_music_on_breaks)
        grid.addWidget(self.play_music_on_breaks_btn, 10, 1, 1, 2)
        self.is_breaks_music_on = False

        grid.setColumnMinimumWidth(0, 50)
        grid.setColumnMinimumWidth(1, 200)
        grid.setColumnMinimumWidth(2, 200)
        grid.setColumnMinimumWidth(3, 50)
        self.setGeometry(400, 300, 500, 400)
        self.setFixedWidth(self.width())
        self.setFixedHeight(self.height())
        self.setWindowTitle(f'Организация звонков. Версия {version}')
        self.show()
        logging.info('Приложение запущено')
